package com.example.demo.gateway.filter;

import cn.hutool.core.collection.CollectionUtil;
import com.example.demo.gateway.entity.GatewayUpstream;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ThreadLocalRandom;

@Slf4j
public class UpstreamReactorLoadBalancer  {
    private Map<String, List<GatewayUpstream>> upstreams;

    public UpstreamReactorLoadBalancer(Map<String, List<GatewayUpstream>> map) {
        log.info("已经加载upsteam:{}", map);
        this.upstreams = map;
    }
    //upstream://serviceId

    public GatewayUpstream choose(String serviceId){
        List<GatewayUpstream> instances = getInstance(serviceId);
        if (CollectionUtil.isEmpty(instances)) {
            log.error("没有可用于 {} 的服务实例...", serviceId);
            throw new RuntimeException("没有可用的服务实例:" + serviceId);
        }
        GatewayUpstream instance = randomWeight(instances);
        return instance;
    }
    private List<GatewayUpstream> getInstance(String serviceId){
        return upstreams.entrySet().stream()
                .filter(sid -> serviceId.equalsIgnoreCase(sid.getKey()))
                .findFirst()
                .map(Map.Entry::getValue)
                .orElse(new ArrayList<>());
    }

    /**
     * 基于权重的随机算法
     * @param instances
     * @return
     */
    private GatewayUpstream randomWeight(List<GatewayUpstream> instances){
        int length = instances.size(); // 总个数
        int totalWeight = 0; // 总权重
        boolean sameWeight = true; // 权重是否都一样
        for (int i = 0; i < length; i++) {
            int weight = instances.get(i).getWeight();
            totalWeight += weight; // 累计总权重
            if (sameWeight && i > 0
                    && weight != instances.get(i).getWeight()) {
                sameWeight = false; // 计算所有权重是否一样
            }
        }
        if (totalWeight > 0 && ! sameWeight) {
            // 如果权重不相同且权重大于0则按总权重数随机
            int offset = ThreadLocalRandom.current().nextInt(totalWeight);
            // 并确定随机值落在哪个片断上
            for (int i = 0; i < length; i++) {
                offset -= instances.get(i).getWeight();
                if (offset < 0) {
                    return instances.get(i);
                }
            }
        }
        // 如果权重相同或权重为0则均等随机
        return instances.get(ThreadLocalRandom.current().nextInt(length));
    }


    public void refresh(Map<String, List<GatewayUpstream>> map) {
        log.info("重新加载upsteam:{}", map);
        this.upstreams = map;
    }

    public Map<String, List<GatewayUpstream>> getUpstreams() {
        return upstreams;
    }
}
